<?php
class Table {
    private static $loadedTables = array();
    protected $connName = null;
    protected $tableName = '';
    /*_dao,_columns,_primaryKey,_uniqueKeys, _indexes等属性无实际意义，仅作标识用，实际调用时不包含下划线，由魔术方法获取属性值*/
    private $_dao         = null;
    private $_columns     = array();
    private $_primaryKey  = array();
    private $_uniqueKeys   = array();
    private $_indexes     = array();
    public static $createTimeArray = array('create_at', 'create_time', 'created', 
                                           'created_at', 'created_time', 'add_time');
    public static $updateTimeArray = array('update_at', 'update_time', 'updated', 'updated_at', 'updated_time', 
                                           'modified', 'modify_time', 'modify_at', 'edit_time', 'edit_at');
    protected $create_at = '';
    protected $update_at = '';
    protected $data = array();
    protected $metadata = null;

    /**
     * 获取表对象实例
     * @param String $tableName
     * @param String $connName
     * @return Table
     */
    public static  function getTable($tableName, $connName = DEFAULT_CONN) {
        $connName = strtoupper($connName);
        $tableName = strtolower($tableName);
        if(false == isset(self::$loadedTables[$connName][$tableName])) {
            self::$loadedTables[$connName][$tableName] = new Table($tableName, $connName);
        }
        
        return self::$loadedTables[$connName][$tableName];
    }
    
    /**
     * 数据表对象构造函数，默认不连接数据库
     * @param String $tableName
     * @param String $connName
     */
    private function __construct($tableName, $connName=DEFAULT_CONN) {
        $dbConns = Config::get('DB_CONN');
        if (false == array_key_exists($connName, $dbConns)) {
            throw new Exception('不存在键值为"' . $connName . '"的数据库连接设置');
        }
        $connSetting = $dbConns[$connName];
        $this->connName = $connName;
        $this->tableName = $tableName;
        $this->getMetadata();
    }
    
    /**
     * 从配置文件或数据库读取表结构
     */
    public function getMetadata() {
        if($this->metadata) return $this->metadata;
        $sysTables = sysTablePath();
        $allTables = $this->dao->getAllTables();
        if (isset($sysTables[$this->tableName])) {
            $metadata = $this->getMetadataFromFile($sysTables[$this->tableName]);
        } else{
            if (Config::get('DEBUG') && in_array($this->tableName, $allTables)) {
                $metadata = $this->getMetadataFromDb($this->tableName);
            } else {
                $tableFile = TABLE_DIR . '/' . $this->connName . '/' . $this->tableName . '.php';
                if (file_exists($tableFile)) {
                    $metadata = $this->getMetadataFromFile($tableFile);
                } else if (in_array($this->tableName, $allTables)){
                    $metadata = $this->getMetadataFromDb($this->tableName);
                } else {
                    throw new Exception('无法获取数据表"' . $this->tableName . '"结构信息');
                }
            }
        }
        
        if (false == in_array($this->tableName, $allTables)) {
            $this->dao->createTable($this->tableName, $metadata);
            $this->import();
        }
        $this->metadata = $metadata;
        return $metadata;
    }

    public function export($condition='1=1') {
        $condition = $this->condition($condition);
        $records = $this->findAll($condition);
        if($records) {
            $exportFile = DBEXPORT_DIR . '/' . $this->connName . '/' . $this->tableName . '.php';
            $exportDir = dirname($exportFile);
            if(false == is_dir($exportDir)) @mkdir($exportDir, 0777, true) or die('数据导出目录不存在且创建失败，请手工创建');
            file_put_contents($exportFile, "<?php\ndie();\n?>" . serialize($records));
        }
    }

    private function import() {
        $exportFile = DBEXPORT_DIR . '/' . $this->connName . '/' . $this->tableName . '.php';
        if(file_exists($exportFile)) {
            Dao::startTrans();
            $contents = file_get_contents($exportFile);
            $contents = substr($contents, 15);
            $records = unserialize($contents);
            foreach($records as $record) {
                $this->save($record);
            }
            Dao::commitTrans();
        }
    }
    
    /**
     * 从数据表配置文件读取数据表结构
     * @param String $tableFile
     * @return array
     */
    private function getMetadataFromFile($tableFile) {
        $metadata = require_once($tableFile);
        if (is_array($metadata)) {
            foreach ($metadata as $key=>$value) {
                $this->$key = $value;
            }
        }
        
        return $metadata;
    }
    
    /**
     * 从数据库读取数据表结构
     * @param String $tableName
     * @param array
     */
    private function getMetadataFromDb($tableName) {
        $metadata = $this->dao->getMetadata($tableName);
        if (is_array($metadata)) {
            $this->saveMetadata($metadata);
            foreach ($metadata as $key=>$value) {
                $this->$key = $value;
            }
        }
        
        return $metadata;
    }
    
    /**
     * 保存表结构元数据
     * @param array $metadata
     */
    private function saveMetadata(&$metadata) {
        $metadataContents = var_export($metadata, true);
        
        $metadataContents = str_replace(
                array('\'PUMP_COL_TYPE_CUSTOM\'', '\'PUMP_COL_TYPE_INCREMENT\'', '\'PUMP_COL_TYPE_UUID\'', "=> \n"),
                array('COL_TYPE_CUSTOM', 'COL_TYPE_INCREMENT', 'COL_TYPE_UUID', "=> "),
                $metadataContents);
        eval('$metadata=' . $metadataContents . ';');
        if (is_dir(TABLE_DIR . '/' . $this->connName) || @mkdir(TABLE_DIR . '/' . $this->connName, 0777, true)) {
            file_put_contents(TABLE_DIR . '/' . $this->connName . '/' . $this->tableName . '.php', "<?php\nreturn " . $metadataContents. ";\n?>");
        }
        
    }
    
    /**
     * 根据条件查询一条数据
     * @param mixed $cond
     * @param String $column
     * @return array
     */
    public function find($condition, $columns = '*', $order ='', $fetchType=PUMP_FETCH_ASSOC) {
        $cond = $this->condition($condition);
        if (false == $cond) {
            throw new Exception('没有设置查找条件,或查询条件中不包含数据表字段相关信息[' . $this->tableName . '::find(' . var_export($condition, true) . ')]');
        }
        $strQuery = 'SELECT ' . $columns . ' FROM ' . $this . ' WHERE ' . $cond;
        if($order) $strQuery .= ' ORDER BY ' . $order;
        return $this->dao->getRow($strQuery);
    }
    

    /**
     * 计算表中符合条件的记录数
     * @param mixed $cond
     * @return int
     */
    public function count($condition) {
        $cond = $this->condition($condition);
        if (false == $cond) throw new Exception('没有设置计算条件,如确定计算所有记录，请设置计算条件为1=1[' . $this->tableName . '::count(' . var_export($condition, true) . ')]');
        $strQuery = 'SELECT COUNT(1) FROM ' . $this . ' WHERE ' . $cond;
        return $this->dao->getOne($strQuery);
    }
    
    public function groupCount($condition, $groupColumns) {
        $cond = $this->condition($condition);
        if(false == $cond) throw new Exception('没有设置计算条件,如确定计算所有记录，请设置计算条件为1=1[' . $this->tableName . '::goupCount(' . var_export($condition, true) . ')]');
        $strQuery = 'SELECT COUNT(1) FROM ' . $this . ' WHERE ' . $cond . ' GROUP BY ' . $groupColumns;
        return $this->dao->getOne($strQuery);
    }
    
    /**
     *计算符合条件的最大字段值
     *@return mixed
     */
    public function max($columnName, $condition) {
        $cond = $this->condition($condition);
        if (false == $cond) throw new Exception('没有设置计算条件,如确定计算所有记录，请设置计算条件为1=1[' . $this->tableName . '::max(' . var_export($condition, true) . ')]');
        $strQuery = 'SELECT MAX(' . $columnName . ') FROM ' . $this . ' WHERE ' . $cond;
        return $this->dao->getOne($strQuery);
    }
    
    /**
     *计算符合条件的最小字段值
     *@return mixed
     */
    public function min($columnName, $condition) {
        $cond = $this->condition($condition);
        if (false == $cond) throw new Exception('没有设置计算条件,如确定计算所有记录，请设置计算条件为1=1[' . $this->tableName . '::min(' . var_export($condition, true) . ')]');
        $strQuery = 'SELECT MIN(' . $columnName . ') FROM ' . $this . ' WHERE ' . $cond;
        return $this->dao->getOne($strQuery);
    }
    
    public function sum($columnName, $condition) {
        $cond = $this->condition($condition);
        if (false == $cond) throw new Exception('没有设置计算条件,如确定计算所有记录，请设置计算条件为1=1[' . $this->tableName . '::sum(' . var_export($condition, true) . ')]');
        $strQuery = 'SELECT SUM(' . $columnName . ') FROM ' . $this . ' WHERE ' . $cond;
        return $this->dao->getOne($strQuery);
    }
    
    /**
     * DISTINCT查询，返回符合条件的经过distinct后的所有记录
     * @return array
     */
    public function distinct($columns, $condition, $order='') {
        return $this->findAll($condition, 'distinct ' . $columns, $order);
    }
    
    /**
     * 根据条件返回多条记录
     * @param mixed $cond
     * @param String $order
     * @param String $column
     * @return array
     */
    public function findAll($condition, $columns='*', $order='', $fetchType=PUMP_FETCH_ASSOC) {
        $cond = $this->condition($condition);
        if (false == $cond) throw new Exception('没有设置查询条件,如确定查询所有记录，请设置查询条件为1=1[' . $this->tableName . '::findAll(' . var_export($condition, true) . ')]');
        $strQuery = 'SELECT ' . $columns . ' FROM ' . $this . ' WHERE ' . $cond;
        if ('' != $order) $strQuery .= ' ORDER BY ' . $order;
        return $this->dao->getAll($strQuery);
    }
    
    
    /**
     * 根据条件返回N条记录
     * @param mixed $cond
     * @param String $order
     * @param String $column
     * @return array
     */
    public function findLimit($condition, $limit, $order='', $columns='*', $fetchType=PUMP_FETCH_ASSOC) {
        $cond = $this->condition($condition);
        if ('' == $cond) throw new Exception('没有设置查询条件,如确定查询所有记录，请设置查询条件为1=1[' . $this->tableName . '::findLimit(' . var_export($condition, true) . ')]');
        $strQuery = 'SELECT ' . $columns . ' FROM ' . $this . ' WHERE ' . $cond;
        if ('' != $order) $strQuery .= ' ORDER BY ' . $order;
        return $this->dao->getLimit($strQuery, $limit);
    }
    
    /**
     * 解析查询条件
     * @param mixed $cond
     * @return String
     */    
    public function condition($cond, $tableAlias='') {
        static $conds = array();
        $condKey = md5(serialize($cond) . $tableAlias);
        if (false == isset($conds[$this->connName][$this->tableName][$condKey])){
            $columns = $this->columns;
            if (is_array($cond)) {
                $conds = array();
                foreach ($cond as $colName=>$colValue) {
                    if (isset($columns[$colName])) {
                        $conds[] = $tableAlias . $colName . ' = ' . $this->renderColumnValue($columns[$colName]['type'], $colValue);
                    }
                }
                if(sizeof($conds) > 0) {
                    $condition = implode(' AND ', $conds);
                    $conds[$this->connName][$this->tableName][$condKey] = $condition;
                    return $condition;
                }
                return false;
            } else {
                if (preg_match('/[\s=<>]+/', $cond)) {
                    return $cond;
                } else {
                    $primaryKey = $this->primaryKey;
                    $keyColumnCount = sizeof($primaryKey);
                    if ($keyColumnCount >0 && 1 != $keyColumnCount) {
                        throw new Exception('数据表"' . $this->tableName . '"主键包含' . $keyColumnCount . '个字段，请使用数组传递相关字段值');
                    } else {
                        $columns = $this->columns;
                        $condition = $tableAlias . $primaryKey[0] . '=' . $this->renderColumnValue($columns[$primaryKey[0]]['type'], $cond) ;
                        $conds[$this->connName][$this->tableName][$condKey] = $condition;
                        return $condition;
                    }
                }
            }
        }
        return $conds[$this->connName][$this->tableName][$condKey];
    }
    
    /**
     * 根据字段类型渲染字段值
     * @param String $columnType
     * @param String $columnValue
     * @return String
     */
    private function renderColumnValue($columnType, $columnValue) {
        return $this->dao->renderColumnValue($columnType, $columnValue);
    }

    private function renderDbTime($columnName) {
        $columnType = $this->columns[$columnName]['type'];
        if (stristr($columnType, 'number')) {
            return time();
        } else if (stristr($columnType, 'date')) {
            return $this->dao->renderColumnValue($columnType, date('Y-m-d H:i:s'));
        }
    }
    
    /**
     * 根据所给条件从数据表中删除记录，条件为空时，如果$data不为空，则根据$data内容进行删除，
     * 如$data为空，则抛出异常，如确定要删除所有数据，请设置条件为"1=1"
     * @param mixed $cond  数组，字符串，整型数字，当为字符串时如长度为32且所有字符均处于MD5字符中(0-9,a-f),
     *                     删除对应主键字符
     * @return boolean
     */
    public function delete($condition = '') {
        $cond = $this->condition($condition);
        if ('' == $cond && false == $this->data) {
            throw new Exception('没有设置删除条件,如确定删除所有记录，请设置删除条件为1=1[' . $this->tableName . '::delete(' . var_export($condition, true) . ')]');
        }
        $strQuery = 'DELETE FROM ' . $this . ' WHERE ' . $cond;
        return $this->dao->execute($strQuery);
    }
    
    /**
     * 保存数据到数据表中,当保存条件不为空时进行更新，否则为插入
     * @param array $data
     * @param mixed $saveCond
     * @retun boolean
     */
    public function save($data, $saveCond='') {
        #解析保存条件
        if ('' != $saveCond) {
            $saveCond = $this->condition($saveCond);
        }
        
        $columns = $this->columns;
        #保存数据预处理,自增字段删除数组相关键值， UUID字段如数据不符，则设置，自定义主键字段如不设置抛出异常
        $increColumns = array();
        $primaryKey = $this->primaryKey;
        if ('' == $saveCond) {
            foreach ($columns as $colName=>$colSetting) {
                if(COL_TYPE_INCREMENT == $colSetting['extra'] || '' != $colSetting['sequence']) {
                    if(isset($data[$colName])) {
                        if(false == $data[$colName]) { 
                            $increColumns[] = $colName;
                            unset($data[$colName]);
                        }
                    } else {
                        $increColumns[] = $colName;
                    }
                } else if(COL_TYPE_UUID == $colSetting['extra'] || COL_TYPE_UUID == $colSetting['default']) {
                    if(false == isset($data[$colName]) || false == isUUID($data[$colName])) {
                        $data[$colName] = UUID();
                    }
                } else if (COL_TYPE_CUSTOM == $colSetting['extra'] && in_array($colName, $primaryKey)) {
                    if (false == isset($data[$colName])) {
                        throw new Exception("数据表“" . $this->tableName . "”中字段“" . $colName . "”为自定义主键，键值必须被设置");
                    }
                } else if (false == isset($data[$colName]) && '' != $colSetting['default']) {
                    $data[$colName] = $colSetting['default'];
                }
            }
        }
        
        $fields = array();
        $values = array();
        $notnullCols = array();
        foreach ($columns as $colName=>$colSetting) {
            if (isset($data[$colName])) {
                $fields[] = $colName;
                $values[] = $this->renderColumnValue($colSetting['type'], $data[$colName]);
            } else {
                if ($colSetting['notnull'] && null === $colSetting['default'] && false == isset($colSetting['extra'])) {
                    $notnullCols[] = $colName;
                }
            }
        }
        #根据数据生成SQL语句
        $strQuery = '';
        if (sizeof($fields) > 0) {
            if ($saveCond) {
                if ($this->update_at && false == isset($data[$this->update_at])) {
                    $updateAt = $this->renderDbTime($this->update_at);
                    $fields[] = $this->update_at;
                    $values[] = $updateAt; 
                    $data[$this->update_at] = str_replace(PUMP_QUOTE_STRING, '', $updateAt);
                }
                $strQuery = 'UPDATE ' . $this . ' SET ';
                foreach($fields as $key=>$colName) {
                    $saves[] = $colName . '=' . $values[$key];
                }
                $strQuery .= implode(',', $saves) . ' WHERE ' . $saveCond;
            } else {
                if (sizeof($notnullCols)) {
                    throw new Exception('数据表“' . $this->tableName . '”中以下字段为非空字段，必须设置值<br />' . implode(',', $notnullCols));
                }
                $this->data = array();
                if ($this->create_at && false == isset($data[$this->create_at])) {
                    $createAt = $this->renderDbTime($this->create_at);
                    $data[$this->create_at] = str_replace(PUMP_QUOTE_STRING, '', $createAt);
                    $fields[] = $this->create_at;
                    $values[] = $createAt;
                }
                $strQuery = 'INSERT INTO ' . $this . ' (' . implode(',', $fields) . ') VALUES (' . implode(',', $values) . ')';
            }
        }
        if ($strQuery) {
            $execResult = $this->dao->execute($strQuery);
            if ($execResult) {
                $this->data = $data;
                if(sizeof($increColumns) > 0) {
                    foreach($increColumns as $columnName) {
                        $sequenceName = '';
                        if($this->dao->sequence) {
                            $sequenceName = $columns[$columnName]['sequence'];
                            if(false == $sequenceName) {
                                $metadata = $this->getMetadataFromDb($this->tableName);
                                $sequenceName = $metadata['columns'][$columnName]['sequence'];
                            }
                        }
                        $sequenceValue = $this->dao->lastInsertId($sequenceName); 
                        $this->data[$columnName] = $sequenceValue;
                        $data[$columnName] = $sequenceValue;
                    }
                }
                $data = &$this->data;
            }
            return $execResult;
        }
        return false;
    }
    
    public function affectRows() {
        return $this->dao->affectRows();
    }
    
    /**
     * 魔术方法获取属性值
     */
    public function __get($varName) {
        if('dao' == strtolower($varName)) {
            $this->dao = Dao::getDao($this->connName);
            return $this->dao;
        } else if (in_array($varName, array('columns', 'keyType', 'primaryKey', 'uniqueKey', 'indexes'))) {
            $this->getMetadata();
            return $this->$varName;
        } else if (array_key_exists($varName, $this->data)) {
            return $this->data[$varName];
        }
    }

    public function createAtColumn() {
        return $this->create_at;
    }

    public function updateAtColumn() {
        return $this->update_at;
    }
    
    /**
     * 魔术方法设置属性值
     */
    public function __set($varName, $varValue) {
        $this->$varName = $varValue;
    }

    public function vacuum() {
        $this->dao->vacuum();
    }
    
    /**
     * 魔术方法返回数据表名，表名前缀为常量PUMP_TABLE_PREFIX,系统调用时前缀被Dao::$tablePrefix替换
     */
    public function __toString() {
        return PUMP_TABLE_PREFIX . $this->tableName;
    }
};
?>